package com.qwert.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qwert.shortlink.admin.common.biz.user.JwtUtil;
import com.qwert.shortlink.admin.common.convention.exception.ClientException;
import com.qwert.shortlink.admin.common.enums.UserErrorCode;
import com.qwert.shortlink.admin.dao.entity.User;
import com.qwert.shortlink.admin.dao.mapper.UserMapper;
import com.qwert.shortlink.admin.dto.req.user.UserLoginReqDTO;
import com.qwert.shortlink.admin.dto.req.user.UserRegisterReqDTO;
import com.qwert.shortlink.admin.dto.req.user.UserUpdateReqDTO;
import com.qwert.shortlink.admin.dto.resp.user.UserLoginRespDTO;
import com.qwert.shortlink.admin.dto.resp.user.UserRespDTO;
import com.qwert.shortlink.admin.service.IGroupService;
import com.qwert.shortlink.admin.service.IUserService;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.qwert.shortlink.admin.common.constant.RedisCacheConstant.LOCK_USER_REGISTER_KEY;
import static com.qwert.shortlink.admin.common.constant.RedisCacheConstant.USER_LOGIN_KEY;
/**
 * 用户接口实现层
 * @author qwert
 * @version 1.0.0
 * @create 2024/6/25 10:07
 **/
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private final static int INSERTED_ROW = 1;
    private final static int EXIST_USER_FLAG = 0;
    private final static String DEFAULT_GROUP_NAME = "默认分组";

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final IGroupService groupService;

    @Override
    public UserRespDTO getUserByUsername(String username) {
        LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getUsername, username);
        User user = baseMapper.selectOne(queryWrapper);
        if(user == null){
            throw new ClientException(UserErrorCode.USER_NULL);
        }
        UserRespDTO result = new UserRespDTO();
        BeanUtils.copyProperties(user, result);
        return result;
    }

    @Override
    public Boolean hasUsername(String username) {
        //LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getUsername, username);
        //User user = baseMapper.selectOne(queryWrapper);
        //return user == null;
        //不存在返回true，存在返回false
        return !userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Override
    public Integer register(UserRegisterReqDTO userRegisterReqDTO) {
        // 判断用户名是否存在
        if(!hasUsername(userRegisterReqDTO.getUsername())){
            throw new ClientException(UserErrorCode.USER_NAME_EXIST);
        }

        // 添加数据库之前尝试获取分布式锁（改锁只会锁同一个用户名张三锁张三，张三不锁李四）
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + userRegisterReqDTO.getUsername());

        try {
            if(lock.tryLock()){
                // 将DTO对象转换成DO对象，并存入数据库
                int inserted = 0;
                try {
                    inserted = baseMapper.insert(BeanUtil.toBean(userRegisterReqDTO, User.class));
                    if(inserted < INSERTED_ROW){
                        throw new ClientException(UserErrorCode.USER_SAVE_ERROR);
                    }
                } catch (Exception e) {
                    throw new ClientException(UserErrorCode.USER_EXIST);                }

                // 添加到布隆过滤器
                userRegisterCachePenetrationBloomFilter.add(userRegisterReqDTO.getUsername());
                // 创建默认分组
                groupService.saveGroup(userRegisterReqDTO.getUsername(), DEFAULT_GROUP_NAME);
                // 返回新增的行数（可选返回新的的id）
                return inserted;
            }
        } catch (Exception e) {
            throw new ClientException(UserErrorCode.USER_SAVE_ERROR);
        } finally {
            // 添加完数据需要释放锁
            lock.unlock();
        }
        return null;
    }

    @Override
    public Integer update(UserUpdateReqDTO userUpdateReqDTO) {
        //TODO 验证当前修改用户的登录状态
        LambdaUpdateWrapper<User> wrapper =Wrappers.lambdaUpdate(User.class)
                .eq(User::getUsername, userUpdateReqDTO.getUsername());
        return baseMapper.update(BeanUtil.toBean(userUpdateReqDTO, User.class), wrapper);
    }

    @Override
    public UserLoginRespDTO login(UserLoginReqDTO userLoginReqDTO) {
        // 校验用户是否存在
        User user = baseMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getUsername, userLoginReqDTO.getUsername())
                .eq(User::getPassword, userLoginReqDTO.getPassword())
                .eq(User::getDelFlag, EXIST_USER_FLAG));

        if(user == null){
            throw new ClientException(UserErrorCode.USER_NULL);
        }
        // 判断用户是否已经登录
        String cacheToken = stringRedisTemplate.opsForValue().get(USER_LOGIN_KEY + userLoginReqDTO.getUsername());

        if(cacheToken != null){
            stringRedisTemplate.expire(USER_LOGIN_KEY + userLoginReqDTO.getUsername(), 30L, TimeUnit.MINUTES);
            Map<String, Object> cacheClaim = JwtUtil.parseToken(cacheToken);
            String cacheUsername = (String) cacheClaim.get("username");
            if(!userLoginReqDTO.getUsername().equals(cacheUsername)){
                throw new ClientException("用户登录校验失败");
            }
            return new UserLoginRespDTO(cacheToken);
        }

        // 用户未登录，将用户保存到token并存入数据库
        Map<String, Object> claim = new HashMap<>();
        claim.put("username", user.getUsername());
//        claim.put("realName", user.getRealName());
        claim.put("id", user.getId());
        String genToken = JwtUtil.genToken(claim);
        stringRedisTemplate.opsForValue().set(USER_LOGIN_KEY + userLoginReqDTO.getUsername(), genToken);
        stringRedisTemplate.expire(USER_LOGIN_KEY + userLoginReqDTO.getUsername(), 30L, TimeUnit.MINUTES);
        // 返回token
        return new UserLoginRespDTO(genToken);
    }

    @Override
    public Boolean checkLogin(String username, String token) {
        return stringRedisTemplate.opsForValue().get(USER_LOGIN_KEY + username) != null;
    }

    @Override
    public void logout(String username, String token) {
        if(checkLogin(username, token)) {
            stringRedisTemplate.delete(USER_LOGIN_KEY + username);
            return;
        }
        throw new ClientException("用户token不存在或者用户未登录");
    }

}
